using SharpGraphT.Event;
using SharpGraphT.Sharp.Sharp.Util;

namespace SharpGraphT.Traverse;

public abstract class
    CrossComponentIterator<TV, TE, TD> : AbstractGraphIterator<TV, TE>
    where TE : class, new() {
    private static readonly int CcsBeforeComponent = 1;
    private static readonly int CcsWithinComponent = 2;
    private static readonly int CcsAfterComponent = 3;

    private Dictionary<TV, TD> _seen = new();

    private IEnumerator<TV> _entireGraphVertexIterator;

    private IEnumerator<TV> _startVertexIterator;

    private TV _startVertex;

    private bool _isStartVertexValid;

    private int _state = CcsBeforeComponent;

    private TV _current;

    private bool _isEnumerationStarted;

    private bool _isEnumerationFinished;

    public CrossComponentIterator(IGraph<TV, TE> g) : this(g, null) { }

    public CrossComponentIterator(IGraph<TV, TE> g, TV startVertex) : this(g,
        startVertex == null ? null : new[] { startVertex }) { }

    public CrossComponentIterator(IGraph<TV, TE> g,
        IEnumerable<TV> startVertices) : base(g) {
        if (startVertices == null) {
            crossComponentTraversal = true;
        } else {
            crossComponentTraversal = false;
            _startVertexIterator = startVertices.GetEnumerator();
        }

        var it = crossComponentTraversal
            ? GetEntireGraphVertexIterator()
            : _startVertexIterator;
        if (it.MoveNext()) {
            _startVertex = it.Current;
            _isStartVertexValid = true;
            if (!g.ContainsVertex(_startVertex)) {
                throw new ArgumentException(
                    "graph must contain the start vertex");
            }
        } else {
            _startVertex = default;
            _isStartVertexValid = false;
        }
    }

    public override bool MoveNext() {
        if (!_isEnumerationStarted) {
            _isEnumerationStarted = true;
        }

        var hasNext = false;

        if (_isStartVertexValid) {
            EncounterStartVertex();
        }

        if (IsConnectedComponentExhausted()) {
            if (_state == CcsWithinComponent) {
                _state = CcsAfterComponent;
                ConnectedComponentFinished?.Invoke(this,
                    new ConnectedComponentTraversalEventArgs(
                        ConnectedComponentTraversalEventArgs
                            .ConnectedComponentFinished));
            }

            var it = IsCrossComponentTraversal
                ? GetEntireGraphVertexIterator()
                : _startVertexIterator;
            while (it != null && it.MoveNext()) {
                var v = it.Current;
                if (!graph.ContainsVertex(v)) {
                    throw new ArgumentException(
                        "graph must contain the start vertex");
                }

                if (IsSeenVertex(v)) {
                    continue;
                }

                EncounterVertex(v, null);
                _state = CcsBeforeComponent;
                hasNext = true;
                break;
            }
        } else {
            hasNext = true;
        }

        if (!hasNext) {
            _isEnumerationFinished = true;
            return false;
        }

        if (_state == CcsBeforeComponent) {
            _state = CcsWithinComponent;
            ConnectedComponentStarted?.Invoke(this,
                new ConnectedComponentTraversalEventArgs(
                    ConnectedComponentTraversalEventArgs
                        .ConnectedComponentStarted));
        }

        _current = ProvideNextVertex();
        VertexTraversed?.Invoke(this,
            new VertexTraversalEventArgs<TV>(_current));

        AddUnseenChildrenOf(_current);
        return true;
    }

    public override TV Current =>
        _isEnumerationStarted
            ? _isEnumerationFinished
                ?
                throw new InvalidOperationException(
                    "Enumeration already finished.")
                : _current
            : throw new InvalidOperationException(
                "Enumeration has not started. Call MoveNext.");

    protected IEnumerator<TV> GetEntireGraphVertexIterator() {
        if (_entireGraphVertexIterator != null) {
            return _entireGraphVertexIterator;
        }

        Objects.Assert(IsCrossComponentTraversal);
        _entireGraphVertexIterator = graph.VertexSet.GetEnumerator();

        return _entireGraphVertexIterator;
    }

    protected abstract bool IsConnectedComponentExhausted();

    protected abstract void EncounterVertex(TV vertex, TE edge);

    protected abstract TV ProvideNextVertex();

    protected TD GetSeenData(TV vertex) => _seen[vertex];

    protected bool IsSeenVertex(TV vertex) => _seen.ContainsKey(vertex);

    protected abstract void EncounterVertexAgain(TV vertex, TE edge);

    protected TD PutSeenData(TV vertex, TD data) => _seen[vertex] = data;

    protected void FinishVertex(TV vertex) =>
        VertexFinished?.Invoke(this, new VertexTraversalEventArgs<TV>(vertex));

    protected IReadOnlySet<TE> SelectOutgoingEdges(TV vertex) =>
        graph.OutgoingEdgesOf(vertex);

    private void AddUnseenChildrenOf(TV vertex) {
        foreach (var edge in SelectOutgoingEdges(vertex)) {
            EdgeTraversed?.Invoke(this, new EdgeTraversalEventArgs<TE>(edge));

            var oppositeV = graph.GetOppositeVertex(edge, vertex);

            if (IsSeenVertex(oppositeV)) {
                EncounterVertexAgain(oppositeV, edge);
            } else {
                EncounterVertex(oppositeV, edge);
            }
        }
    }

    private void EncounterStartVertex() {
        EncounterVertex(_startVertex, null);
        _startVertex = default;
        _isStartVertexValid = false;
    }

    public override void Reset() => throw new NotSupportedException();

    public override void Dispose() { }

    public override event EventHandler<ConnectedComponentTraversalEventArgs>
        ConnectedComponentFinished;

    public override event EventHandler<ConnectedComponentTraversalEventArgs>
        ConnectedComponentStarted;

    public override event EventHandler<EdgeTraversalEventArgs<TE>>
        EdgeTraversed;

    public override event EventHandler<VertexTraversalEventArgs<TV>>
        VertexTraversed;

    public override event EventHandler<VertexTraversalEventArgs<TV>>
        VertexFinished;
}